all repos — caroster @ 8a4f9e069b406ad3b9c2405dbef767b2a5304477

[Octree] Group carpool to your event https://caroster.io

frontend/pages/e/[uuid]/details.tsx (view raw)

  1import moment from 'moment';
  2import Linkify from 'linkify-react';
  3import Tooltip from '@mui/material/Tooltip';
  4import IconButton from '@mui/material/IconButton';
  5import Box from '@mui/material/Box';
  6import Link from '@mui/material/Link';
  7import Card from '@mui/material/Card';
  8import Container from '@mui/material/Container';
  9import TextField from '@mui/material/TextField';
 10import Typography from '@mui/material/Typography';
 11import TuneIcon from '@mui/icons-material/Tune';
 12import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
 13import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
 14import {DatePicker} from '@mui/x-date-pickers/DatePicker';
 15import {PropsWithChildren, useState} from 'react';
 16import {useTranslation} from 'next-i18next';
 17import pageUtils from '../../../lib/pageUtils';
 18import DetailsLink from '../../../containers/DetailsLink';
 19import ShareEvent from '../../../containers/ShareEvent';
 20import PlaceInput from '../../../containers/PlaceInput';
 21import LangSelector from '../../../components/LangSelector';
 22import usePermissions from '../../../hooks/usePermissions';
 23import useEventStore from '../../../stores/useEventStore';
 24import useToastStore from '../../../stores/useToastStore';
 25import EventLayout, {TabComponent} from '../../../layouts/Event';
 26import {
 27  EventByUuidDocument,
 28  useUpdateEventMutation,
 29} from '../../../generated/graphql';
 30import {langLocales} from '../../../locales/constants';
 31import {getLocaleForLang} from '../../../lib/getLocale';
 32import theme from '../../../theme';
 33
 34interface Props {
 35  eventUUID: string;
 36  announcement?: string;
 37}
 38
 39const Page = (props: PropsWithChildren<Props>) => {
 40  return <EventLayout {...props} Tab={DetailsTab} />;
 41};
 42
 43const DetailsTab: TabComponent<Props> = ({}) => {
 44  const {t} = useTranslation();
 45  const {
 46    userPermissions: {canEditEventDetails},
 47  } = usePermissions();
 48  const [updateEvent] = useUpdateEventMutation();
 49  const addToast = useToastStore(s => s.addToast);
 50  const setEventUpdate = useEventStore(s => s.setEventUpdate);
 51  const event = useEventStore(s => s.event);
 52  const [isEditing, setIsEditing] = useState(false);
 53
 54  if (!event) return null;
 55
 56  const hasGeoloc = event.latitude && event.longitude;
 57
 58  const onSave = async e => {
 59    try {
 60      const {uuid, ...data} = event;
 61      delete data.linkedEvent;
 62      delete data.isReturnEvent;
 63      const {
 64        id,
 65        travels,
 66        waitingPassengers,
 67        __typename,
 68        administrators,
 69        passengers,
 70        ...input
 71      } = data;
 72      await updateEvent({
 73        variables: {
 74          uuid,
 75          eventUpdate: {
 76            ...input,
 77          },
 78        },
 79        refetchQueries: ['eventByUUID'],
 80      });
 81      setIsEditing(false);
 82    } catch (error) {
 83      console.error(error);
 84      addToast(t('event.errors.cant_update'));
 85    }
 86  };
 87
 88  const modifyButton = isEditing ? (
 89    <Tooltip
 90      title={t('event.details.save')}
 91      sx={{
 92        position: 'absolute',
 93        top: theme.spacing(2),
 94        right: theme.spacing(2),
 95      }}
 96    >
 97      <IconButton color="primary" onClick={onSave}>
 98        <CheckCircleOutlineIcon />
 99      </IconButton>
100    </Tooltip>
101  ) : (
102    <Tooltip
103      title={t('event.details.modify')}
104      sx={{
105        position: 'absolute',
106        top: theme.spacing(2),
107        right: theme.spacing(2),
108      }}
109    >
110      <IconButton color="primary" onClick={() => setIsEditing(true)}>
111        <TuneIcon />
112      </IconButton>
113    </Tooltip>
114  );
115
116  return (
117    <Box
118      sx={{
119        position: 'relative',
120      }}
121    >
122      <Container
123        sx={{
124          p: 4,
125          mt: 6,
126          mb: 11,
127          mx: 0,
128          [theme.breakpoints.down('md')]: {
129            p: 2,
130            mt: 13,
131          },
132        }}
133      >
134        <Card
135          sx={{
136            position: 'relative',
137            maxWidth: '100%',
138            width: '480px',
139            p: 2,
140          }}
141        >
142          <Typography variant="h4" pb={2}>
143            {t('event.details')}
144          </Typography>
145          {canEditEventDetails() && modifyButton}
146          {(isEditing || event.name) && (
147            <Box pt={2} pr={1.5}>
148              <Typography variant="overline">
149                {t('event.fields.name')}
150              </Typography>
151              <Typography>
152                {isEditing ? (
153                  <TextField
154                    size="small"
155                    fullWidth
156                    value={event.name}
157                    onChange={e => setEventUpdate({name: e.target.value})}
158                    name="name"
159                    id="EditEventName"
160                  />
161                ) : (
162                  <Typography id="EventName">{event.name}</Typography>
163                )}
164              </Typography>
165            </Box>
166          )}
167          {(isEditing || event.date) && (
168            <Box pt={2} pr={1.5}>
169              <Typography variant="overline">
170                {t('event.fields.date')}
171              </Typography>
172              {isEditing ? (
173                <Typography>
174                  <DatePicker
175                    slotProps={{
176                      textField: {
177                        size: 'small',
178                        id: `EditEventDate`,
179                        fullWidth: true,
180                        placeholder: t('event.fields.date_placeholder'),
181                      },
182                    }}
183                    format="DD/MM/YYYY"
184                    value={moment(event.date)}
185                    onChange={date =>
186                      setEventUpdate({
187                        date: !date ? null : moment(date).format('YYYY-MM-DD'),
188                      })
189                    }
190                  />
191                </Typography>
192              ) : (
193                <Box position="relative">
194                  <Typography id="EventDate">
195                    {moment(event.date).format('DD/MM/YYYY')}
196                  </Typography>
197                </Box>
198              )}
199            </Box>
200          )}
201          {(isEditing || event.address) && (
202            <Box pt={2} pr={1.5}>
203              <Typography variant="overline">
204                {t('event.fields.address')}
205              </Typography>
206              {isEditing ? (
207                <PlaceInput
208                  place={event.address}
209                  latitude={event.latitude}
210                  longitude={event.longitude}
211                  onSelect={({place, latitude, longitude}) =>
212                    setEventUpdate({
213                      address: place,
214                      latitude,
215                      longitude,
216                    })
217                  }
218                />
219              ) : (
220                <Box position="relative">
221                  <Typography
222                    id="EventAddress"
223                    title={t`placeInput.noCoordinates`}
224                    sx={{
225                      pr: 3,
226                      display: 'inline-flex',
227                      alignItems: 'center',
228                      columnGap: 1,
229                    }}
230                  >
231                    <Link
232                      target="_blank"
233                      rel="noreferrer"
234                      href={`https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(
235                        event.address
236                      )}`}
237                      onClick={e => e.preventDefault}
238                    >
239                      {event.address}
240                    </Link>
241                    {!hasGeoloc && (
242                      <InfoOutlinedIcon fontSize="small" color="warning" />
243                    )}
244                  </Typography>
245                </Box>
246              )}
247            </Box>
248          )}
249          {(isEditing || event.description) && (
250            <Box pt={2} pr={1.5}>
251              <Typography variant="overline">
252                {t('event.fields.description')}
253              </Typography>
254              {isEditing ? (
255                <Typography>
256                  <TextField
257                    fullWidth
258                    multiline
259                    maxRows={4}
260                    inputProps={{maxLength: 250}}
261                    value={event.description || ''}
262                    onChange={e =>
263                      setEventUpdate({description: e.target.value})
264                    }
265                    id={`EditEventDescription`}
266                    name="description"
267                  />
268                </Typography>
269              ) : (
270                <Typography
271                  id="EventDescription"
272                  sx={{pr: 3, whiteSpace: 'pre-line'}}
273                >
274                  <Linkify options={{render: DetailsLink}}>
275                    {event.description}
276                  </Linkify>
277                </Typography>
278              )}
279            </Box>
280          )}
281          {(isEditing || event.lang) && (
282            <Box pt={2} pr={1.5}>
283              <Typography variant="overline">
284                {t('event.fields.lang')}
285              </Typography>
286              {isEditing ? (
287                <LangSelector
288                  value={event.lang}
289                  onChange={lang => setEventUpdate({lang})}
290                />
291              ) : (
292                <Typography id="EventLang" sx={{pr: 3}}>
293                  {langLocales[event.lang]}
294                </Typography>
295              )}
296            </Box>
297          )}
298          {!isEditing && !!event.email && (
299            <Box pt={2} pr={1.5}>
300              <Typography variant="overline">
301                {t('options.plus.creator')}
302              </Typography>
303              <Typography id="EventLang" sx={{pr: 3}}>
304                {event.email}
305              </Typography>
306            </Box>
307          )}
308          {!isEditing && (
309            <ShareEvent
310              title={`Caroster ${event.name}`}
311              sx={{width: '100%', mt: 2}}
312            />
313          )}
314        </Card>
315      </Container>
316    </Box>
317  );
318};
319
320export const getServerSideProps = pageUtils.getServerSideProps(
321  async (context, apolloClient) => {
322    const {uuid} = context.query;
323    const {host = ''} = context.req.headers;
324    let event = null;
325
326    // Fetch event
327    try {
328      const {data} = await apolloClient.query({
329        query: EventByUuidDocument,
330        variables: {uuid},
331      });
332      event = data?.eventByUUID?.data;
333    } catch (error) {
334      return {
335        notFound: true,
336      };
337    }
338
339    const description = await getLocaleForLang(
340      event?.attributes?.lang,
341      'meta.description'
342    );
343
344    return {
345      props: {
346        eventUUID: uuid,
347        metas: {
348          title: event?.attributes?.name || '',
349          description,
350          url: `https://${host}${context.resolvedUrl}`,
351        },
352      },
353    };
354  }
355);
356export default Page;